import { ConfigPreview } from "@/docs/components/Preview";

# Dynamic Props

Dynamic prop resolution allows you to change the props for a component after the props have been changed by the user. This is useful for making third-party API calls, such as requesting the latest content from a headless CMS.

## Dynamic component props

The [`resolveData` function](/docs/api-reference/configuration/component-config#resolvedatadata-params) allows you to make changes to the props and set fields as read-only.

For example, we can set the value of one prop to another:

```tsx {12-18} showLineNumbers copy
const config = {
  components: {
    HeadingBlock: {
      fields: {
        title: {
          type: "text",
        },
        resolvedTitle: {
          type: "text",
        },
      },
      resolveData: async ({ props }) => {
        return {
          props: {
            resolvedTitle: props.title,
          },
        };
      },
      render: ({ resolvedTitle }) => {
        return <h1>{resolvedTitle}</h1>;
      },
    },
  },
};
```

<ConfigPreview
  label='Try changing the "title" field'
  componentConfig={{
    fields: {
      title: {
        type: "text",
      },
      resolvedTitle: {
        type: "text",
      },
    },
    defaultProps: {
      title: "Hello, world",
    },
    resolveData: ({ props }) => {
      return {
        props: { resolvedTitle: props.title },
      };
    },
    render: ({ resolvedTitle }) => {
      return <p style={{ margin: 0 }}>{resolvedTitle}</p>;
    },

}}
/>

> When inserting components with `resolveData`, the Puck state will update twice - once for the initial insert, and once more when the method resolves, if it changes the data. This will be reflected in the undo/redo history.

### Setting fields as read-only

[`resolveData`](/docs/api-reference/configuration/component-config#resolvedatadata-params) also allows us to mark fields as read-only using the [`readOnly` parameter](/docs/api-reference/configuration/component-config#datareadonly-1).

```tsx {17} showLineNumbers copy
const config = {
  components: {
    HeadingBlock: {
      // ...
      resolveData: async ({ props }) => {
        return {
          props: {
            resolvedTitle: props.title,
          },
          readOnly: { resolvedTitle: true },
        };
      },
      // ...
    },
  },
};
```

<ConfigPreview
  label='The resolvedTitle field is locked'
  componentConfig={{
    fields: {
      title: {
        type: "text",
      },
      resolvedTitle: {
        type: "text",
      },
    },
    defaultProps: {
      title: "Hello, world",
    },
    resolveData: ({ props }) => {
      return {
        props: { resolvedTitle: props.title },
        readOnly: { resolvedTitle: true }
      };
    },
    render: ({ resolvedTitle }) => {
      return <p style={{ margin: 0 }}>{resolvedTitle}</p>;
    },

}}
/>

### Preventing duplicate calls

It's possible that `resolveData` may carry out an expensive operation (like an API call) that we want to avoid making unless a specific prop has changed.

This can be restricted by checking the [`changed` param](/docs/api-reference/configuration/component-config#paramschanged) before calling any expensive operations.

```tsx {6} showLineNumbers copy
const config = {
  components: {
    HeadingBlock: {
      // ...
      resolveData: async ({ props }, { changed }) => {
        if (!changed.text) return { props };

        return {
          props: {
            resolvedTitle: await expensiveOperation(props.title),
          },
        };
      },
      // ...
    },
  },
};
```

## Dynamic Root props

The `resolveData` method is also available on the [root component](/docs/api-reference/configuration/config#root).

```tsx showLineNumbers copy {12-18}
const config = {
  components: {},
  root: {
    fields: {
      title: {
        type: "text",
      },
      resolvedTitle: {
        type: "text",
      },
    },
    resolveData: async ({ props }) => {
      return {
        props: {
          resolvedTitle: props.title,
        },
      };
    },
    render: ({ children, resolvedTitle }) => {
      return (
        <>
          <h1>{resolvedTitle}</h1>
          {children}
        </>
      );
    },
  },
};
```

## Triggering `resolveData`

Resolve data is triggered whenever the props for a component change, or when the [`resolveAllData` utility](/docs/api-reference/functions/resolve-all-data) is used.

```tsx
import { resolveAllData } from "@measured/puck";

const updatedData = await resolveAllData(data, config);
```

## Further reading

- [`resolveData` API reference](/docs/api-reference/configuration/component-config#resolvedatadata-params)
- [`resolveAllData` API reference](/docs/api-reference/functions/resolve-all-data)
